require_relative 'func'
require_relative 'slot'
require_relative 'lib'

$last_numbers = Array.new

def expand_build_number_macro queue, register, number

	# get number path
	# build the number in register

	cur = 0
	if (register.field.respond_to?("<") && register.field > number)
		queue.push_command(register.index, Put, :right)
		queue.push_command(register.index, Zero, :left)
		cur = 0
	end
	
	if (register.field.respond_to?("name") && register.field.name != "Zero")
		queue.push_command(register.index, Put, :right) if register.field.name != "I"
		queue.push_command(register.index, Zero, :left)
		cur = 0
	end

	if (register.field.respond_to?("<") && register.field <= number)
		cur = register.field
		
		if path(number, cur).size > path(number, 0).size + 2
			queue.push_command(register.index, Put, :right)
			queue.push_command(register.index, Zero, :left)
			cur = 0
		end
			
	end
	
	steps = path(number, cur).reverse
	
	steps.each do |f|
		queue.push_command(register.index, f, :right)
	end
	
	#puts "Building number path #{number} for register #{register}, len = #{queue.size}"
		
end

# add macro command for deferred building number
# it will allow to build actual commands based on the current state of registers
# and minimize required turns to build it
def build_number slot, number
	$command_queue.push(BuildNumberCommand.new(slot, number))
end

def register_cost r, number
	reset_cost = 0
	cur = 0
	if r[:slot].field.respond_to?("<")
		cur = r[:slot].field
		if cur > number
			cur = 0
			reset_cost = 2
		end
	elsif r[:slot].field.name == "I"
		reset_cost = 1
	elsif r[:slot].field.name != "Zero"
		reset_cost = 2
	end
	cost = reset_cost + r[:cost] + path(number, cur).size
	if cur > 0
		reset_cost = 2
		cost0 = reset_cost + r[:cost] + path(number, 0).size
		cost = [cost, cost0].min
	end
	cost += 10 if r[:slot].index == 0 and number > 255 #add penalty for big numbers and 0 register
	
	if r[:slot].field.respond_to?("<") && $last_numbers.count(r[:slot].field.to_i) > 2 && number != r[:slot].field.to_i
		#add penalty when the register contains a recently used number
		cost += 3*$last_numbers.count(r[:slot].field.to_i) 
	end
	
	cost
end

def put_number slot, number
	raise "Dead slot #{slot}" if slot.dead?
	
	# if number is small then it is cheaper to contruct it directly on call stack and do not use a register
	if number == 0
		$command_queue.push_command(slot.index, Zero, :left)
	elsif number == 1
		call_func(slot, Succ, number-1)
	else
		register = $registers.find_all { |r|
			!r[:slot].dead?
		}.min { |a, b|
			register_cost(a, number) <=> register_cost(b, number) }
		
		build_number(register[:slot], number)
		call_func(slot, Get, register[:slot].index)
		
		$last_numbers.push number if number > 255
		$last_numbers.shift if $last_numbers.size > 10
	end

end

def call_func slot, f, x
	$command_queue.push_command(slot.index, K, :right)
	$command_queue.push_command(slot.index, S, :right)
	$command_queue.push_command(slot.index, f, :left)
	
	if x == 0 
		$command_queue.push_command(slot.index,  Zero, :left)
	else
		call_func(slot, Succ, x-1)		
	end
end


def attack slot, from, to, power
	#puts "Attack #{slot} #{from} #{to} #{power}"
	
	raise "Dead slot #{slot}" if slot.dead?
	
	$command_queue.push_command(slot.index, Attack, :left)
	
	put_number(slot, from)
	put_number(slot, to)
	put_number(slot, power)
end

def help slot, from, to, power
	#puts "Help #{slot} #{from} #{to} #{power}"
	
	raise "Dead slot #{slot}" if slot.dead?
	raise "Power is 0 #{slot}" if power == 0
	
	$command_queue.push_command(slot.index, Help, :left)
	
	put_number(slot, from)
	put_number(slot, to)
	put_number(slot, power)
end

def revive slot, target
	#puts "revive #{slot} #{target}"
	
	raise "Dead slot #{slot}" if slot.dead?
	
	$command_queue.push_command(slot.index, Revive, :left)
	
	put_number(slot, target)
end

def copy slot, target
	raise "Dead slot #{slot}" if slot.dead?

	$command_queue.push_command(slot.index, Copy, :left)

	put_number(slot, target)
end